package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"github.com/cheggaaa/pb/v3"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"os/exec"
	"regexp"
	"strconv"
	"strings"
	"sync"
)

type Bangumi struct {
	Code    int    `json:"code"`
	Message string `json:"message"`
	Result  struct {
		Activity struct {
			HeadBgUrl string `json:"head_bg_url"`
			Id        int    `json:"id"`
			Title     string `json:"title"`
		} `json:"activity"`
		Alias string `json:"alias"`
		Areas []struct {
			Id   int    `json:"id"`
			Name string `json:"name"`
		} `json:"areas"`
		BkgCover string `json:"bkg_cover"`
		Cover    string `json:"cover"`
		Episodes []struct {
			Aid       int    `json:"aid"`
			Badge     string `json:"badge"`
			BadgeInfo struct {
				BgColor      string `json:"bg_color"`
				BgColorNight string `json:"bg_color_night"`
				Text         string `json:"text"`
			} `json:"badge_info"`
			BadgeType int    `json:"badge_type"`
			Bvid      string `json:"bvid"`
			Cid       int    `json:"cid"`
			Cover     string `json:"cover"`
			Dimension struct {
				Height int `json:"height"`
				Rotate int `json:"rotate"`
				Width  int `json:"width"`
			} `json:"dimension"`
			Duration    int    `json:"duration"`
			From        string `json:"from"`
			Id          int    `json:"id"`
			IsViewHide  bool   `json:"is_view_hide"`
			Link        string `json:"link"`
			LongTitle   string `json:"long_title"`
			PubTime     int    `json:"pub_time"`
			Pv          int    `json:"pv"`
			ReleaseDate string `json:"release_date"`
			Rights      struct {
				AllowDemand   int `json:"allow_demand"`
				AllowDm       int `json:"allow_dm"`
				AllowDownload int `json:"allow_download"`
				AreaLimit     int `json:"area_limit"`
			} `json:"rights"`
			ShareCopy string `json:"share_copy"`
			ShareUrl  string `json:"share_url"`
			ShortLink string `json:"short_link"`
			Status    int    `json:"status"`
			Subtitle  string `json:"subtitle"`
			Title     string `json:"title"`
			Vid       string `json:"vid"`
		} `json:"episodes"`
		Evaluate string `json:"evaluate"`
		Freya    struct {
			BubbleDesc    string `json:"bubble_desc"`
			BubbleShowCnt int    `json:"bubble_show_cnt"`
			IconShow      int    `json:"icon_show"`
		} `json:"freya"`
		JpTitle string `json:"jp_title"`
		Link    string `json:"link"`
		MediaId int    `json:"media_id"`
		Mode    int    `json:"mode"`
		NewEp   struct {
			Desc  string `json:"desc"`
			Id    int    `json:"id"`
			IsNew int    `json:"is_new"`
			Title string `json:"title"`
		} `json:"new_ep"`
		Payment struct {
			Discount int `json:"discount"`
			PayType  struct {
				AllowDiscount    int `json:"allow_discount"`
				AllowPack        int `json:"allow_pack"`
				AllowTicket      int `json:"allow_ticket"`
				AllowTimeLimit   int `json:"allow_time_limit"`
				AllowVipDiscount int `json:"allow_vip_discount"`
				ForbidBb         int `json:"forbid_bb"`
			} `json:"pay_type"`
			Price             string `json:"price"`
			Promotion         string `json:"promotion"`
			Tip               string `json:"tip"`
			ViewStartTime     int    `json:"view_start_time"`
			VipDiscount       int    `json:"vip_discount"`
			VipFirstPromotion string `json:"vip_first_promotion"`
			VipPromotion      string `json:"vip_promotion"`
		} `json:"payment"`
		Positive struct {
			Id    int    `json:"id"`
			Title string `json:"title"`
		} `json:"positive"`
		Publish struct {
			IsFinish      int    `json:"is_finish"`
			IsStarted     int    `json:"is_started"`
			PubTime       string `json:"pub_time"`
			PubTimeShow   string `json:"pub_time_show"`
			UnknowPubDate int    `json:"unknow_pub_date"`
			Weekday       int    `json:"weekday"`
		} `json:"publish"`
		Rating struct {
			Count int     `json:"count"`
			Score float64 `json:"score"`
		} `json:"rating"`
		Record string `json:"record"`
		Rights struct {
			AllowBp         int    `json:"allow_bp"`
			AllowBpRank     int    `json:"allow_bp_rank"`
			AllowDownload   int    `json:"allow_download"`
			AllowReview     int    `json:"allow_review"`
			AreaLimit       int    `json:"area_limit"`
			BanAreaShow     int    `json:"ban_area_show"`
			CanWatch        int    `json:"can_watch"`
			Copyright       string `json:"copyright"`
			ForbidPre       int    `json:"forbid_pre"`
			FreyaWhite      int    `json:"freya_white"`
			IsCoverShow     int    `json:"is_cover_show"`
			IsPreview       int    `json:"is_preview"`
			OnlyVipDownload int    `json:"only_vip_download"`
			Resource        string `json:"resource"`
			WatchPlatform   int    `json:"watch_platform"`
		} `json:"rights"`
		SeasonId    int    `json:"season_id"`
		SeasonTitle string `json:"season_title"`
		Seasons     []struct {
			Badge     string `json:"badge"`
			BadgeInfo struct {
				BgColor      string `json:"bg_color"`
				BgColorNight string `json:"bg_color_night"`
				Text         string `json:"text"`
			} `json:"badge_info"`
			BadgeType           int    `json:"badge_type"`
			Cover               string `json:"cover"`
			HorizontalCover1610 string `json:"horizontal_cover_1610"`
			HorizontalCover169  string `json:"horizontal_cover_169"`
			MediaId             int    `json:"media_id"`
			NewEp               struct {
				Cover     string `json:"cover"`
				Id        int    `json:"id"`
				IndexShow string `json:"index_show"`
			} `json:"new_ep"`
			SeasonId    int    `json:"season_id"`
			SeasonTitle string `json:"season_title"`
			SeasonType  int    `json:"season_type"`
			Stat        struct {
				Favorites    int `json:"favorites"`
				SeriesFollow int `json:"series_follow"`
				Views        int `json:"views"`
			} `json:"stat"`
		} `json:"seasons"`
		Section []struct {
			EpisodeId int `json:"episode_id"`
			Episodes  []struct {
				Aid       int    `json:"aid"`
				Badge     string `json:"badge"`
				BadgeInfo struct {
					BgColor      string `json:"bg_color"`
					BgColorNight string `json:"bg_color_night"`
					Text         string `json:"text"`
				} `json:"badge_info"`
				BadgeType int    `json:"badge_type"`
				Bvid      string `json:"bvid"`
				Cid       int    `json:"cid"`
				Cover     string `json:"cover"`
				Dimension struct {
					Height int `json:"height"`
					Rotate int `json:"rotate"`
					Width  int `json:"width"`
				} `json:"dimension"`
				Duration    int    `json:"duration"`
				From        string `json:"from"`
				Id          int    `json:"id"`
				IsViewHide  bool   `json:"is_view_hide"`
				Link        string `json:"link"`
				LongTitle   string `json:"long_title"`
				PubTime     int    `json:"pub_time"`
				Pv          int    `json:"pv"`
				ReleaseDate string `json:"release_date"`
				Rights      struct {
					AllowDemand   int `json:"allow_demand"`
					AllowDm       int `json:"allow_dm"`
					AllowDownload int `json:"allow_download"`
					AreaLimit     int `json:"area_limit"`
				} `json:"rights"`
				ShareCopy string `json:"share_copy"`
				ShareUrl  string `json:"share_url"`
				ShortLink string `json:"short_link"`
				Stat      struct {
					Coin     int `json:"coin"`
					Danmakus int `json:"danmakus"`
					Likes    int `json:"likes"`
					Play     int `json:"play"`
					Reply    int `json:"reply"`
				} `json:"stat"`
				Status   int    `json:"status"`
				Subtitle string `json:"subtitle"`
				Title    string `json:"title"`
				Vid      string `json:"vid"`
			} `json:"episodes"`
			Id    int    `json:"id"`
			Title string `json:"title"`
			Type  int    `json:"type"`
		} `json:"section"`
		Series struct {
			SeriesId    int    `json:"series_id"`
			SeriesTitle string `json:"series_title"`
		} `json:"series"`
		ShareCopy     string `json:"share_copy"`
		ShareSubTitle string `json:"share_sub_title"`
		ShareUrl      string `json:"share_url"`
		Show          struct {
			WideScreen int `json:"wide_screen"`
		} `json:"show"`
		SquareCover string `json:"square_cover"`
		Stat        struct {
			Coins     int `json:"coins"`
			Danmakus  int `json:"danmakus"`
			Favorite  int `json:"favorite"`
			Favorites int `json:"favorites"`
			Likes     int `json:"likes"`
			Reply     int `json:"reply"`
			Share     int `json:"share"`
			Views     int `json:"views"`
		} `json:"stat"`
		Status   int    `json:"status"`
		Subtitle string `json:"subtitle"`
		Title    string `json:"title"`
		Total    int    `json:"total"`
		Type     int    `json:"type"`
		UpInfo   struct {
			Avatar   string `json:"avatar"`
			Follower int    `json:"follower"`
			IsFollow int    `json:"is_follow"`
			Mid      int    `json:"mid"`
			Pendant  struct {
				Image string `json:"image"`
				Name  string `json:"name"`
				Pid   int    `json:"pid"`
			} `json:"pendant"`
			ThemeType  int    `json:"theme_type"`
			Uname      string `json:"uname"`
			VerifyType int    `json:"verify_type"`
			VipLabel   struct {
				LabelTheme string `json:"label_theme"`
				Path       string `json:"path"`
				Text       string `json:"text"`
			} `json:"vip_label"`
			VipStatus int `json:"vip_status"`
			VipType   int `json:"vip_type"`
		} `json:"up_info"`
		UserStatus struct {
			AreaLimit    int `json:"area_limit"`
			BanAreaShow  int `json:"ban_area_show"`
			Follow       int `json:"follow"`
			FollowStatus int `json:"follow_status"`
			Login        int `json:"login"`
			Pay          int `json:"pay"`
			PayPackPaid  int `json:"pay_pack_paid"`
			Sponsor      int `json:"sponsor"`
		} `json:"user_status"`
	} `json:"result"`
}

type PageList struct {
	Code    int    `json:"code"`
	Message string `json:"message"`
	Ttl     int    `json:"ttl"`
	Data    []struct {
		Cid       int    `json:"cid"`
		Page      int    `json:"page"`
		From      string `json:"from"`
		Part      string `json:"part"`
		Duration  int    `json:"duration"`
		Vid       string `json:"vid"`
		Weblink   string `json:"weblink"`
		Dimension struct {
			Width  int `json:"width"`
			Height int `json:"height"`
			Rotate int `json:"rotate"`
		} `json:"dimension"`
		FirstFrame string `json:"first_frame,omitempty"`
	} `json:"data"`
}

type VideoData struct {
	Code    int    `json:"code"`
	Message string `json:"message"`
	Ttl     int    `json:"ttl"`
	Data    []struct {
		Cid       int    `json:"cid"`
		Page      int    `json:"page"`
		From      string `json:"from"`
		Part      string `json:"part"`
		Duration  int    `json:"duration"`
		Vid       string `json:"vid"`
		Weblink   string `json:"weblink"`
		Dimension struct {
			Width  int `json:"width"`
			Height int `json:"height"`
			Rotate int `json:"rotate"`
		} `json:"dimension"`
	} `json:"data"`
}

type Result struct {
	Code    int    `json:"code"`
	Message string `json:"message"`
	Ttl     int    `json:"ttl"`
	Data    struct {
		From              string   `json:"from"`
		Result            string   `json:"result"`
		Message           string   `json:"message"`
		Quality           int      `json:"quality"`
		Format            string   `json:"format"`
		Timelength        int      `json:"timelength"`
		AcceptFormat      string   `json:"accept_format"`
		AcceptDescription []string `json:"accept_description"`
		AcceptQuality     []int    `json:"accept_quality"`
		VideoCodecid      int      `json:"video_codecid"`
		SeekParam         string   `json:"seek_param"`
		SeekType          string   `json:"seek_type"`
		Dash              struct {
			Duration       int     `json:"duration"`
			MinBufferTime  float64 `json:"minBufferTime"`
			MinBufferTime1 float64 `json:"min_buffer_time"`
			Video          []struct {
				Id            int      `json:"id"`
				BaseUrl       string   `json:"baseUrl"`
				BaseUrl1      string   `json:"base_url"`
				BackupUrl     []string `json:"backupUrl"`
				BackupUrl1    []string `json:"backup_url"`
				Bandwidth     int      `json:"bandwidth"`
				MimeType      string   `json:"mimeType"`
				MimeType1     string   `json:"mime_type"`
				Codecs        string   `json:"codecs"`
				Width         int      `json:"width"`
				Height        int      `json:"height"`
				FrameRate     string   `json:"frameRate"`
				FrameRate1    string   `json:"frame_rate"`
				Sar           string   `json:"sar"`
				StartWithSap  int      `json:"startWithSap"`
				StartWithSap1 int      `json:"start_with_sap"`
				SegmentBase   struct {
					Initialization string `json:"Initialization"`
					IndexRange     string `json:"indexRange"`
				} `json:"SegmentBase"`
				SegmentBase1 struct {
					Initialization string `json:"initialization"`
					IndexRange     string `json:"index_range"`
				} `json:"segment_base"`
				Codecid int `json:"codecid"`
			} `json:"video"`
			Audio []struct {
				Id            int      `json:"id"`
				BaseUrl       string   `json:"baseUrl"`
				BaseUrl1      string   `json:"base_url"`
				BackupUrl     []string `json:"backupUrl"`
				BackupUrl1    []string `json:"backup_url"`
				Bandwidth     int      `json:"bandwidth"`
				MimeType      string   `json:"mimeType"`
				MimeType1     string   `json:"mime_type"`
				Codecs        string   `json:"codecs"`
				Width         int      `json:"width"`
				Height        int      `json:"height"`
				FrameRate     string   `json:"frameRate"`
				FrameRate1    string   `json:"frame_rate"`
				Sar           string   `json:"sar"`
				StartWithSap  int      `json:"startWithSap"`
				StartWithSap1 int      `json:"start_with_sap"`
				SegmentBase   struct {
					Initialization string `json:"Initialization"`
					IndexRange     string `json:"indexRange"`
				} `json:"SegmentBase"`
				SegmentBase1 struct {
					Initialization string `json:"initialization"`
					IndexRange     string `json:"index_range"`
				} `json:"segment_base"`
				Codecid int `json:"codecid"`
			} `json:"audio"`
			Dolby interface{} `json:"dolby"`
		} `json:"dash"`
		SupportFormats []struct {
			Quality        int    `json:"quality"`
			Format         string `json:"format"`
			NewDescription string `json:"new_description"`
			DisplayDesc    string `json:"display_desc"`
			Superscript    string `json:"superscript"`
		} `json:"support_formats"`
		HighFormat interface{} `json:"high_format"`
	} `json:"data"`
	Session string `json:"session"`
}

type I struct {
	ChatServer string   `xml:"chatserver"`
	ChatId     string   `xml:"chatid"`
	D          []string `xml:"d"`
}

func main() {
	for {
		fmt.Println("请选择下载视频的类型(无法下载大会员视频):")
		fmt.Println(`1.单视频下载(默认下载最高画质)
2.分P视频批量下载(默认下载最高画质)
3.下载番剧(默认下载最高画质)
4.退出`)
		var sel int
		fmt.Scanf("%d\n", &sel)
		if sel == 4 {
			os.Exit(0)
		}
		fmt.Println("请输入B站视频链接:")
		var path string
		fmt.Scanf("%s\n", &path)
		if path == "Exit" || path == "exit" {
			return
		}
		if sel > 3 || sel <= 1 {
			getVideo(path, "", nil)
		} else if sel == 2 {
			split := strings.Split(path, "?")
			mainPath := split[0]
			getMultiList(mainPath)
		} else {
			split := strings.Split(path, "?")
			mainPath := split[0]
			getBangumi(mainPath)
		}
	}
}

func getBangumi(mainPath string) {
	var wg sync.WaitGroup
	index := strings.LastIndex(mainPath, "/")
	epOrss := mainPath[index+1:]
	esid := epOrss[2:]
	var pagePath string
	if strings.Contains(epOrss, "ep") {
		pagePath = "https://api.bilibili.com/pgc/view/web/season?ep_id=" + esid
	} else if strings.Contains(epOrss, "ss") {
		pagePath = "https://api.bilibili.com/pgc/view/web/season?season_id=" + esid
	}

	resp, err := http.Get(pagePath)
	if err != nil {
		fmt.Println("get pageList err:", err)
	}
	defer resp.Body.Close()
	readAll, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		fmt.Println("ioRead resp err:", err)
	}
	var res Bangumi
	json.Unmarshal(readAll, &res)
	var ind = 0
	for _, episode := range res.Result.Episodes {
		if episode.Badge == "" {
			id := episode.Id
			path := "https://www.bilibili.com/bangumi/play/ep" + strconv.Itoa(id)
			wg.Add(1)
			go getVideo(path, episode.ShareCopy, &wg)
		}
		if ind == 3 {
			break
		}
		ind++
	}
	wg.Wait()
}

func getMultiList(mainPath string) {
	var wg sync.WaitGroup
	index := strings.LastIndex(mainPath, "/")
	bvid := mainPath[index+1:]
	pageListPath := "https://api.bilibili.com/x/player/pagelist?bvid=" + bvid + "&jsonp=jsonp"
	resp, err := http.Get(pageListPath)
	if err != nil {
		fmt.Println("get pageList err:", err)
	}
	defer resp.Body.Close()
	readAll, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		fmt.Println("ioRead resp err:", err)
	}
	var res PageList
	json.Unmarshal(readAll, &res)
	for _, page := range res.Data {
		name := page.Part
		path := mainPath + "?p=" + strconv.Itoa(page.Page)
		wg.Add(1)
		go getVideo(path, name, &wg)
	}
	wg.Wait()
}

func getVideo(path, addTitle string, wg *sync.WaitGroup) {
	resp, _ := http.Get(path)
	defer resp.Body.Close()
	b, _ := ioutil.ReadAll(resp.Body)
	src := string(b)
	re, _ := regexp.Compile("<title data-vue-meta=\"true\">(.*?)</title>")
	var title string
	if addTitle == "" {
		title = re.FindString(src)
		title = strings.Replace(title, "<title data-vue-meta=\"true\">", "", 1)
		title = strings.Replace(title, "</title>", "", 1)
		fmt.Println("标题:" + title)
		fmt.Println("是否重命名视频？（Y/N）")
		var flag string
		fmt.Scanln(&flag)
		if flag == "Y" || flag == "y" {
			fmt.Println("请输入视频名称:")
			fmt.Scanln(&title)
		}
	} else {
		title = addTitle
		fmt.Println("标题:" + title)
	}

	re, _ = regexp.Compile("__playinfo__=(.*?)</script>")
	findString := re.FindString(src)
	findString = strings.Replace(findString, "__playinfo__=", "", 1)
	findString = strings.Replace(findString, "</script>", "", 1)
	var da Result
	json.Unmarshal([]byte(findString), &da)
	fmt.Println("播放时长:", da.Data.Dash.Duration/60, "分钟")

	fmt.Println("开始下载视频[" + title + "]请耐心等待:")
	downloadVideoSingle(da.Data.Dash.Video[0].BaseUrl, da.Data.Dash.Audio[0].BaseUrl, title)

	if wg != nil {
		wg.Done()
	}
}

func downloadVideoSingle(video_url, audio_url, title string) {
	video_request, _ := http.NewRequest("GET", video_url, nil)
	video_request.Header.Set("Referer", "https://www.bilibili.com")

	video_content, _ := http.DefaultClient.Do(video_request)
	defer video_content.Body.Close()

	_, err := os.Stat("./video")
	if os.IsNotExist(err) {
		os.Mkdir("./video", 777)
	}

	create, _ := os.Create(fmt.Sprintf("%s%s%s.mp4", "./video/", title, "video"))
	i, _ := strconv.Atoi(video_content.Header.Get("Content-Length"))
	sourceSiz := int64(i)
	fmt.Printf("视频大小:%.2dMb\n", sourceSiz/1024/1024)
	bar := pb.Full.Start64(sourceSiz)
	barReader := bar.NewProxyReader(video_content.Body)
	io.Copy(create, barReader)
	bar.Finish()

	audio_request, _ := http.NewRequest("GET", audio_url, nil)
	audio_request.Header.Set("Referer", "https://www.bilibili.com")

	audio_content, _ := http.DefaultClient.Do(audio_request)
	defer audio_content.Body.Close()

	_, err = os.Stat("./audio")
	if os.IsNotExist(err) {
		os.Mkdir("./audio", 777)
	}
	c2, _ := os.Create(fmt.Sprintf("%s%s%s.mp4", "./audio/", title, "audio"))

	audioI, _ := strconv.Atoi(audio_content.Header.Get("Content-Length"))
	audioSourceSiz := int64(audioI)
	fmt.Printf("音频大小:%.2dMb\n", audioSourceSiz/1024/1024)
	audioBar := pb.Full.Start64(audioSourceSiz)
	audioBarReader := audioBar.NewProxyReader(audio_content.Body)
	io.Copy(c2, audioBarReader)
	audioBar.Finish()

	fmt.Println("开始合并视频[" + title + "]")
	mergeAudioVedio(title)
	fmt.Println("[" + title + "]合并结束!")
}

func mergeAudioVedio(title string) {
	_, err := os.Stat("./output")
	if os.IsNotExist(err) {
		os.Mkdir("./output", 777)
	}
	vn := fmt.Sprintf("%s%s%s", "./video/", title, "video.mp4")
	an := fmt.Sprintf("%s%s%s", "./audio/", title, "audio.mp4")
	op := fmt.Sprintf("%s%s%s", "./output/", title, ".mp4")
	command := exec.Command("./bin/ffmpeg", "-i", vn, "-i", an, "-c:v", "copy", "-c:a", "aac", "-strict", "experimental", op)
	var out bytes.Buffer
	var stderr bytes.Buffer
	command.Stdout = &out
	command.Stderr = &stderr
	err = command.Run()
	if err != nil {
		fmt.Println(fmt.Sprint(err) + ": " + stderr.String())
		return
	}
}
